Tujuan dalam proyek ini adalah untuk mengeksplorasi data transaksi FnB dari 1 Desember 2017 hingga 18 Februari 2018. Tantangan dari proyek ini adalah untuk memperkirakan jumlah pengunjung per jam, yang akan dievaluasi pada 7 hari ke depan [Senin, 19 Desember 2017 sampai Minggu, 25 Desember 2017].
library(tidyverse)
library(lubridate)
library(padr)
library(tseries)
library(TSstudio)
library(zoo)
library(fpp)
library(forecast)
library(TTR)
library(MLmetrics)
library(readxl)Load library yang akan dibutuhkan dalam proyek ini.
fnb <- read.csv("data/data-train.csv")
head(fnb)The dataset includes information about:
glimpse(fnb)#> Rows: 137,748
#> Columns: 10
#> $ transaction_date <chr> "2017-12-01T13:32:46Z", "2017-12-01T13:32:46Z", "2017~
#> $ receipt_number <chr> "A0026694", "A0026694", "A0026695", "A0026695", "A002~
#> $ item_id <chr> "I10100139", "I10500037", "I10500044", "I10400009", "~
#> $ item_group <chr> "noodle_dish", "drinks", "drinks", "side_dish", "drin~
#> $ item_major_group <chr> "food", "beverages", "beverages", "food", "beverages"~
#> $ quantity <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1,~
#> $ price_usd <dbl> 7.33, 4.12, 2.02, 5.60, 3.01, 4.86, 6.34, 7.58, 4.12,~
#> $ total_usd <dbl> 7.33, 4.12, 2.02, 5.60, 3.01, 4.86, 6.34, 7.58, 4.12,~
#> $ payment_type <chr> "cash", "cash", "cash", "cash", "cash", "cash", "cash~
#> $ sales_type <chr> "dine_in", "dine_in", "dine_in", "dine_in", "dine_in"~
summary(fnb)#> transaction_date receipt_number item_id item_group
#> Length:137748 Length:137748 Length:137748 Length:137748
#> Class :character Class :character Class :character Class :character
#> Mode :character Mode :character Mode :character Mode :character
#>
#>
#>
#> item_major_group quantity price_usd total_usd
#> Length:137748 Min. : 1.000 Min. : 1.03 Min. : 1.030
#> Class :character 1st Qu.: 1.000 1st Qu.: 3.13 1st Qu.: 4.120
#> Mode :character Median : 1.000 Median : 4.61 Median : 4.860
#> Mean : 1.163 Mean : 4.88 Mean : 5.534
#> 3rd Qu.: 1.000 3rd Qu.: 6.22 3rd Qu.: 6.340
#> Max. :36.000 Max. :18.82 Max. :326.150
#> payment_type sales_type
#> Length:137748 Length:137748
#> Class :character Class :character
#> Mode :character Mode :character
#>
#>
#>
anyNA(fnb)#> [1] FALSE
Setelah kita cek data FnB diatas, kita akan rubah atau ringkas/ mutate transaction_date sebagai data date dan dijadikan sebagai jam/ hour. Dan tipe data yang tidak sesuai dan kolom baru sebagai count_visitor
Kita rubah transaction_date menjadi date :
fnb$transaction_date <- ymd_hms(fnb$transaction_date)rubah dijadikan menjadikan sebagai jam/hour
fnb <- fnb %>%
mutate(datetime= floor_date(transaction_date, unit ="hour"))DISTINCT digunakan untuk memastikan tidak ada dua baris data atau lebih yang menampilkan nilai yang sama dari receipt_number:
fnb <- fnb %>%
group_by(datetime) %>%
summarise(count_visitor = n_distinct(receipt_number))Untuk memastikan tidak ada waktu yang hilang dalam data kita, mari lakukan time series padding, membulatkan tanggal/jam dan interval awal, akhir untuk padding
min_date <- min(fnb$datetime)
max_date <- max(fnb$datetime)fnb <- fnb %>%
pad(start_val = make_datetime(year = year(min_date),
month = month(min_date),
day= day(min_date),
hour = 0),
end_val = make_datetime(year = year(max_date),
month = month(max_date),
day= day(max_date),
hour = 23))Karena transaksinya per jam, kita perlu mengubah tipe data menjadi jam dan setelah kita tahu bahwa Restoran hanya dibuka pada 10:00 hingga 22:00, dan kita perlu memfilter dataset dari jam 10:00 sampai 20:00 dan mengisi nilai NA dengan 0
fnb_fin <- fnb %>%
mutate(count_visitor = replace_na(count_visitor,0)) %>%
filter(hour(datetime) >=10& hour(datetime) <=22)
fnb_fin <- fnb_fin[-c(1:3),]Mari buat FnB time series dan simpan sebagai fnb_ts
fnb_ts <- ts(data=fnb_fin$count_visitor,start = c(1,4), frequency = 13)Periksa data di simple plot
fnb_ts %>%
autoplot()+
theme_minimal()fnb_ts %>%
tail(13*7*4) %>%
stl(s.window = "periodic") %>%
autoplot() fnb_single_decompose <- decompose(fnb_ts)fnb_msts <- msts(data = fnb_fin$count_visitor,seasonal.periods = c(13,13*7))fnb_msts %>%
tail(13*7*4) %>%
stl(s.window = "periodic") %>%
autoplot() fnb_double_decompose <- mstl(fnb_msts)fnb_fin %>%
mutate(
seasonal = fnb_single_decompose$seasonal,
hour = hour(datetime)
) %>%
distinct(hour, seasonal) %>%
ggplot(mapping = aes(x = hour, y = seasonal)) +
geom_col() +
theme_minimal() +
scale_x_continuous(breaks = seq(10,22,1)) +
labs(
title = "Single Seasonality Plot"
)as.data.frame(fnb_double_decompose) %>%
mutate(datetime = fnb_fin$datetime) %>%
mutate(
dow = wday(datetime, label = TRUE, abbr = FALSE),
hour = as.factor(hour(datetime))
) %>%
group_by(dow, hour) %>%
summarise(seasonal = sum(Seasonal13 + Seasonal91)) %>%
ggplot(mapping = aes(x = hour, y = seasonal)) +
geom_col(aes(fill = dow)) +
scale_fill_viridis_d(option = "plasma") +
theme_minimal() +
labs(
title = "Multiseasonality Plot"
)Diperlukan Cross Validation sebelum melakukan time analisa
ts_info(fnb_ts)#> The fnb_ts series is a ts object with 1 variable and 1037 observations
#> Frequency: 13
#> Start time: 1 4
#> End time: 80 13
Diperlukan membagi data atau splitting antara data train dan data_test
train_fnb_ts <- head(fnb_ts, n = length(fnb_ts)-13*7)
test_fnb_ts <- tail(fnb_ts, n = 13*7)secara default fungsi HoltWinters() akan mencari parameter smoothing yang menurutnya optimal.
Forecasting dengan fungsi forecast() dan model evaluation menggunakan fungsi MAE
# modeling
model_tes_ts <- HoltWinters(train_fnb_ts)
# forecast
forecast_tes_ts <- forecast(model_tes_ts, h = 13*7)
# model evaluation
MAE(y_pred = forecast_tes_ts$mean, y_true = test_fnb_ts)#> [1] 11.61221
memvisualisasikan jumlah pengunjung aktual vs perkiraan
test_forecast(actual = fnb_ts ,
forecast.obj = forecast_tes_ts,
train = train_fnb_ts,
test = test_fnb_ts)model_arima_ts <- stlm(train_fnb_ts, method = "arima")
# forecast
forecast_arima_ts <- forecast(model_arima_ts, h=13*7)
# model evaluation
MAE(y_pred = forecast_arima_ts$mean, y_true = test_fnb_ts)#> [1] 7.461238
test_forecast(actual = fnb_ts ,
forecast.obj = forecast_arima_ts,
train = train_fnb_ts,
test = test_fnb_ts)train_fnb_msts <- head(fnb_msts, n= length(fnb_msts)-(13*7))
test_fnb_msts <- tail(fnb_msts, n=13*7)model_tes_msts <- HoltWinters(train_fnb_msts)# forecast
forecast_tes_msts <- forecast(model_tes_msts, h = 13*7)
# model evaluation
MAE(y_pred = forecast_tes_msts$mean, y_true = test_fnb_msts)#> [1] 6.451498
test_forecast(actual = fnb_msts ,
forecast.obj = forecast_tes_msts,
train = train_fnb_msts,
test = test_fnb_msts)model_arima_msts <- stlm(train_fnb_msts, method = "arima")
# forecast
forecast_arima_msts <- forecast(model_arima_msts, h=13*7)
# model evaluation
MAE(y_pred = forecast_arima_msts$mean, y_true = test_fnb_msts)#> [1] 5.656791
test_forecast(actual = fnb_msts ,
forecast.obj = forecast_arima_msts,
train = train_fnb_msts,
test = test_fnb_msts)Mari kita coba mengimplementasikan ke data data-test.csv untuk hasil akhir dari model kita.
test_data <- read.csv("data/data-test.csv")
model_arima_test <- stlm(fnb_msts, method = "arima")
# forecast
forecast_arima_test <- forecast(model_arima_test, h=13*7)
# insert the data into table
test_data$visitor <- forecast_arima_test$mean
write.csv(test_data,file = "submission-david.csv", row.names = F)
head(test_data,3)Kinerja model yang dihasilkan adalah 4,8 dari target 6. Artinya model kami cukup baik.
Pengecekan asumsi dan summary dari result
Asumsi Autocorrelation, menggunakan fungsi Ljung-box
Box.test(model_arima_test$residuals, type = "Ljung-Box")#>
#> Box-Ljung test
#>
#> data: model_arima_test$residuals
#> X-squared = 0.0081534, df = 1, p-value = 0.9281
Hasil : p-value=0.9578 > 0.05 (alpha), maka dapat disimpulkan bahwa residual tidak memiliki autokorelasi.
shapiro.test(x = model_arima_test$residuals)#>
#> Shapiro-Wilk normality test
#>
#> data: model_arima_test$residuals
#> W = 0.99114, p-value = 0.000006817
Karena nilai p lebih kecil dari 0,05, maka residu tidak terdistribusi secara normal. Perhatikan bahwa uji Shapiro hanya menguji penyimpangan distribusi residual dari normal dan bukan kinerja prakiraan, yang memburuk untuk prakiraan yang lebih lama. Jika kita ingin memperkirakan data yang lebih panjang, kita perlu menambahkan lebih banyak data untuk dimasukkan ke dalam model kita.
Adapun yang terakhir, bisa kita lihat dari Analisis Musiman, kita menyimpulkan bahwa hari Sabtu pukul 20.00 atau 8 malam adalah pengunjung tertinggi ke restoran.